/* 
 * This program come form network.
 * 
 * this file implemention U-boot dnw download file to RAM on GNU/Linux system
 * dependent on secbulk driver.
 *
 */
#include "secbulk.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_SUCCESS 1
#endif

#define DNW_DEV		"/dev/secbulk0"
#define LOAD_ADDR	(0x32000000)

#define max(a, b) (a < b ? b : a)

struct dnw_head {
    unsigned long load_addr;
    unsigned long trans_size;
};

struct dnw {
    struct dnw_head head;
    int fil_fd;
    off_t fil_siz;
    int dev_fd;
    unsigned short sum; /* transmit checksum */
};

static ssize_t read2write(int read_fd, int write_fd, void *buffer, size_t size) {
    ssize_t remain, n;

    if ((remain = read(read_fd, buffer, size)) < 0) {
        error(0, errno, "Read file error");
        return -1;
    }

    size = remain;

    while ((n = write(write_fd, buffer, remain)) != remain) {
        if (n < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
                continue;
            else {
                error(0, errno, "write data error");
                return -1;
            }
        }
        buffer += n;
        remain -= n;
    }

    return size;
}

static int trans_file(int src_fd, struct stat *srcfstat, int dest_fd,
        unsigned short *sum) {
    int i;
    ssize_t n;
    unsigned char *buf;
    size_t bufsize = max(BULKOUT_BUFFER_SIZE, srcfstat->st_blksize);
    size_t transed = 0;
    size_t remain = srcfstat->st_size;

    if (!(buf = malloc(bufsize))) {
        error(0, ENOMEM, "malloc");
        return -1;
    }

    while (remain) {
        if ((n = read2write(src_fd, dest_fd, buf, bufsize)) < 0) {
            free(buf);
            return -1;
        }

        remain -= n;
        transed += n;

        printf("\r%.1f%%\t "
#if __SIZEOF_SIZE_T__ == __SIZEOF_INT__
                "%u"
#else
                "%lu"
#endif
                " Bytes", transed / (srcfstat->st_size / 100.0), transed);
        fflush(stdout);

        /* checksum */
        for (i = 0; i < n; i++)
            *sum += buf[i];
    }

    free(buf);

    return 0;
}

void usage(int exit_val) {
    printf("Usage: dwn <filename>\n");
    exit(exit_val);
}

int main(int argc, char* argv[]) {
    struct dnw dnw = { .sum = 0 };

    if (argc != 2)
        usage(EXIT_FAILURE);

    if (strncmp(argv[1], "-h", 2) == 0 || strncmp(argv[1], "--help", 6) == 0)
        usage(EXIT_SUCCESS);

    /* open file */
    if ((dnw.fil_fd = open(argv[1], O_RDONLY)) < 0)
        error(-1, errno, "Cannot open %s", argv[1]);
    printf("File name : %s\n", argv[1]);

    /* get file size */
    struct stat fil_stat;
    if (fstat(dnw.fil_fd, &fil_stat) < 0)
        error(-1, errno, "Get file info filed");
    dnw.fil_siz = fil_stat.st_size;
    printf("File size : %lu(%#lx) Bytes\n", dnw.fil_siz, dnw.fil_siz);

    /* open device file */
    if ((dnw.dev_fd = open(DNW_DEV, O_WRONLY)) < 0)
        error(-1, errno, "Cannot open device %s", DNW_DEV);

    dnw.head.load_addr = LOAD_ADDR;
    dnw.head.trans_size = sizeof(dnw.head) + dnw.fil_siz + sizeof(dnw.sum);

    printf("Transmitting ...\n");

    /* transmit head */
    if (sizeof(dnw.head) != write(dnw.dev_fd, &dnw.head, sizeof(dnw.head)))
        error(-1, errno, "transmit head error");

    /* transmit file */
    if (trans_file(dnw.fil_fd, &fil_stat, dnw.dev_fd, &dnw.sum) < 0)
        exit(EXIT_FAILURE);

    printf("\nchecksum:%#x\n", dnw.sum);

    /* transmit checksum */
    if (sizeof(dnw.sum) != write(dnw.dev_fd, &dnw.sum, sizeof(dnw.sum)))
        error(-1, errno, "transmit checksum error");

    printf("Done\n");

    close(dnw.dev_fd);
    close(dnw.fil_fd);

    exit(EXIT_SUCCESS);
}
